package checkers3d.presentation;

import checkers3d.storage.*;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.*;
import javax.swing.*;

/**
 * An object which manages a render window and system input. Allows for drawing
 * objects, which implement IDrawable or GUIContainer, to a invisible buffer.
 * Provides functions for clearing the invisible buffer or copying its contents
 * to the visible buffer. Captures input and redirects to anIInputObserver given
 * when this object is instanced.
 *
 * @author      Ruben Acuna
 */
public class InteractiveWindowNative implements IInteractiveWindow
{
    /**
     * GraphicsEnvironment object for render context.
     */
    private GraphicsEnvironment ge;

    /**
     * GraphicsConfiguration object for render context.
     */
    private GraphicsConfiguration gc;

    /**
    * The JFrame (window) that the this object draws to and receives input from.
    */
    private JFrame frame;
    
    /**
    * JPanel where visible drawing happens.
    */
    private JPanel panel;

    /**
    * An invisible buffer that currently drawing objects write to prevent
    * visible flickering.
    */
    private VolatileImage backBuffer;

    /**
    * Dimenions of the JPanel and backBuffer.
    */
    private Point renderWindowSize;

    /**
     * Height of the titlebar. I guess. Java is stupid and doesn't create a
     * window with the size you request, it makes one this much shorter.
     */
    private final int TITLEBAR_OFFSET = 26;

    /**
    * Class constructor that does nothing. For design purposes, the code that
    * needs to execute when the object is instanced exists in Start() to give
    * GameEngine control over it.
    */
    InteractiveWindowNative()
    {
        //This function intentionally left blank.
    }

    /**
    * Prepare the back buffer for rendering by clearing its contents.
    *
    * @see IInteractiveWindow
    */
    public void prepareBackBuffer()
    {
        //TODO: ???
    }

    /**
    * Copies the contents of the back buffer to the visible front buffer.
    *
    * @see IInteractiveWindow
    */
    public void flushBackBuffer()
    {
        if(backBuffer.validate(gc) == VolatileImage.IMAGE_INCOMPATIBLE)
            backBuffer = gc.createCompatibleVolatileImage(renderWindowSize.x, renderWindowSize.y);
        
        panel.getGraphics().drawImage(backBuffer, 0, 0, frame);
    }

    /**
    * Draws a GUIContainer to the back buffer by looping through the IDrawables
    * it contains and drawing them.
    *
    * @see IInteractiveWindow
    */
    public void draw(GUIContainer window)
    {
        for(IDrawable component : window.getComponentsList())
        {
            draw(component);
        }
    }

    /**
    * Draws an IDrawable to the back buffer based on the RenderResource that is
    * attached to it and the objects position.
    *
    * @param  drawable An object that implements the IDrawable interface.
    * @see IInteractiveWindow
    */
    public void draw(IDrawable drawable)
    {
        Graphics2D render = (Graphics2D)backBuffer.getGraphics();

        //The RenderResource is bitmap based, so we copy the image data to the
        //appropriate location on the buffer.
        if(drawable.getRenderResource() instanceof RenderResourceBitmap)
        {
            RenderResourceBitmap resource
                    = (RenderResourceBitmap)drawable.getRenderResource();

            //HACK?: if scale is 0 or less, affine transform fails, so force it
            //       to always above 0.
            if(resource.getScale() <= 0)
                resource.setScale(0.0000000001f);

            AffineTransform at = AffineTransform.getScaleInstance(resource.getScale(), resource.getScale());
            
            at.rotate(resource.getRotation());

            AffineTransformOp aop = new AffineTransformOp(at, AffineTransformOp.TYPE_BILINEAR);

            render.drawImage(resource.getImage(),
                             aop,
                             drawable.getDrawPosition().x,
                             drawable.getDrawPosition().y);
        }
        //The RenderResource is text based, so we draw the string using the
        //color and text the object contains.
        else if(drawable.getRenderResource() instanceof RenderResourceText)
        {
            RenderResourceText resource
                    = (RenderResourceText)drawable.getRenderResource();

            render.setColor(resource.getColor());
            render.drawString(resource.getText(),
                              drawable.getDrawPosition().x,
                              drawable.getDrawPosition().y + drawable.getRenderResource().getHeight());

            //NOTE: we add k to the y position because drawString draws at the
            //bottom left instead of the top left.

        }
        //The RenderResource is playing surfaced based, so we it by listing
        //through the contents of the board and drawing each object in each
        //square.
        else if(drawable.getRenderResource() instanceof RenderResourcePlayingSurface2D)
        {
            RenderResourcePlayingSurface2D resource
                    = (RenderResourcePlayingSurface2D)drawable.getRenderResource();

            //draw the contents of the square.
            for(int x = 0; x < resource.getWidthTiles(); x++)
                for(int y = 0; y < resource.getHeightTiles(); y++)
                {
                    //draw the contents of the current square
                    for(IDrawable tile : resource.getSurfaceBoardObjects(x, y))
                        //PlayingSurface can only contain IBoardObjects which
                        //will always inmplement IDrawableSelectiveVisibility.
                        if(((IDrawableSelectiveVisibility)tile).isVisibleTo(resource.getViewer()))
                            if(tile.getRenderResource() instanceof RenderResourceBitmap)
                                render.drawImage(((RenderResourceBitmap)tile.getRenderResource()).getImage(),
                                     drawable.getDrawPosition().x + x * resource.getTileSize(),
                                     drawable.getDrawPosition().y + y * resource.getTileSize(), frame);

                    //draw the overlay if it is not null
                    Color overlayColor = resource.getSurfaceOverlayColor(x, y);

                    if(overlayColor != null)
                    {
                        render.setColor(overlayColor);

                        render.drawRect(drawable.getDrawPosition().x + x * resource.getTileSize(),
                                        drawable.getDrawPosition().y + y * resource.getTileSize(),
                                        resource.getTileSize(),
                                        resource.getTileSize());
                    }
                }
        }
        //The RenderResource is a pixel-based rectangle primitive.
        else if(drawable.getRenderResource() instanceof RenderResourceRectangle)
        {
            RenderResourceRectangle resource
                    = (RenderResourceRectangle)drawable.getRenderResource();

            render.setColor(resource.getColor());

            render.fillRect(drawable.getDrawPosition().x,
                            drawable.getDrawPosition().y,
                            resource.width,
                            resource.height);
        }
        //Unknown RenderResource.
        else if (drawable.getRenderResource() != null)
            Logger.log(toString() + " - Encountered unknown RenderResource!");
    }

    /**
    * Closes the window and stops directing input by disposing of the JFrame.
    *
    * @see IInteractiveWindow
    */
    public void shutdown()
    {
        frame.dispose();
    }

    /**
    * Sets up up a render window by creating a JFrame and adding a JPanel to it
    * as a render buffer. Also attaches a given object to events received from
    * the window.
    *
    * @param  windowTitle   String to display in titlebar of window.
    * @param  windowSize    Size of window.
    * @param  userListener  Object that will listen to events received from the
    *                       window.
    * @see IInteractiveWindow
    */
    public void start(String windowTitle, Point windowSize,
                      IInputObserver userListener)
    {
        frame = new JFrame(windowTitle);
        panel = new JPanel();

        frame.setIconImage(new ImageIcon("checkers-icon.png").getImage());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(windowSize.x, windowSize.y + TITLEBAR_OFFSET);
        frame.setVisible(true);
        frame.setResizable(false);

        renderWindowSize = windowSize;

        panel.setPreferredSize(new Dimension(renderWindowSize.x,
                                             renderWindowSize.y));
        panel.setLayout(null);

        frame.add(panel);

        InteractiveWindowNativeClickDetector listener
                = new InteractiveWindowNativeClickDetector(userListener);

        panel.addMouseListener(listener);
        frame.addKeyListener(listener);

        ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        gc = ge.getDefaultScreenDevice().getDefaultConfiguration();

        backBuffer = gc.createCompatibleVolatileImage(renderWindowSize.x,
                                                      renderWindowSize.y);
    }

    /**
    * Returns a string representation of the InteractiveWindow which contains
    * information about the JFrame, JPanel, and window size.
    *
    * @return       A string representation of this GameEngine.
    */
    @Override
    public String toString()
    {
        return getClass().getName() + " -> "
                                    + frame.toString()
                                    + ", " + panel.toString()
                                    + ", " + renderWindowSize.toString();
    }
}